<?php

namespace App\Console\Commands;

use App\Models\Agent;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;

abstract class ForEachAllAgents extends Command
{
    protected function foreachAllAgents(callable $callable, callable $agentWhen = null)
    {
        try {
            $this->agentCount = Agent::query()->count();
            $bar = $this->output->createProgressBar($this->agentCount);
            $bar->start();

            Agent::query()
                ->withTrashed()
                ->select(['id'])
                ->when($agentWhen !== null, fn($query) => $agentWhen($query)) // 提供外部接口
                ->each(function (Agent $agent) use ($bar, $callable) {
                    // 是否已经完成过了 (第一道判断, 省去去获取锁的消耗)
                    if (0 !== Redis::hexists($this->signature, $agent['id'])) {
                        return false;
                    }

                    Cache::lock(sprintf('%s_%d_lock', $this->signature, $agent['id']), 1 * 60 * 60)
                        ->get(function () use ($agent, $callable) {
                            // 是否已经完成过了 (第二道判断, 防止有进程通过了第一道, 然后此时释放锁, 就会再一次获取锁并进入处理)
                            if (0 !== Redis::hexists($this->signature, $agent['id'])) {
                                return false;
                            }

                            $result = null;
                            try {
                                \DB::beginTransaction();

                                $result = $callable($agent);

                                \DB::commit();
                            } catch (\Exception $e) {
                                \DB::rollBack();

                                Redis::hset($this->signature . '_error', $agent['id'], $e->getMessage());
                                return false;
                            }

                            // 已完成
                            Redis::hset($this->signature, $agent['id'], $result ?? '');
                            return true;
                        });

                    $bar->advance(1);
                    return true;
                }, 5000);
            $bar->finish();
        } catch (\Exception $e) {
            $this->output->error($e->getMessage());
        }
    }
}
